/**
 * @file uCOS3_heap.c
 * @author Linquan  
 * @brief  使用前需要调用 umem_block_create 函数创建内存池
 *          不同于freeRTOS，ucos分配的是固定大小的内存块。
 *          需要根据需求，设置不同大小的内存池，用于分配
 * @version 1.0
 * @date 2024-06-13
 * 
 * @copyright Copyright (c) 2024
 * 
 */

#include "stdio.h"
#include "uCOS3_heap.h"


#define  OS_CFG_DBG_EN                   0u      //调试使能
#define  OS_SAFETY_CRITICAL              1u     //判断传入 错误指针 是否为0
#define  OS_CFG_ARG_CHK_EN               1u      //使能传入参数检查


//////////////以下内容直接 照搬UCOS3的 相关变量 保证编译能通过，如有统一需求，就把下面的内容对应更改////////////////////////////////////////////////////////////////////

#define  CPU_TYPE_CREATE(char_1, char_2, char_3, char_4)        (((CPU_INT32U)((CPU_INT08U)(char_1)) << (0u * 8)) | \
                                                                 ((CPU_INT32U)((CPU_INT08U)(char_2)) << (1u * 8)) | \
                                                                 ((CPU_INT32U)((CPU_INT08U)(char_3)) << (2u * 8)) | \
                                                                 ((CPU_INT32U)((CPU_INT08U)(char_4)) << (3u * 8)))
#define  OS_OBJ_TYPE_MEM                 (OS_OBJ_TYPE)CPU_TYPE_CREATE('M', 'E', 'M', ' ')


#define OS_CRITICAL_ENTER()                     //进入临界区
#define OS_CRITICAL_EXIT_NO_SCHED()             //退出临界区
#define CPU_CRITICAL_EXIT() 
#define CPU_CRITICAL_ENTER()




/////////////////////////////////////---END---////////////////////////////////////////////////////////



#if OS_CFG_DBG_EN > 0u
        OS_MEM                           *OSMemDbgListPtr;                          //调试列表指针
#endif

#ifndef OS_OBJ_QTY
typedef   uint16_t                      OS_OBJ_QTY;           /* Number of kernel objects counter <16>/32 */
#endif
OS_OBJ_QTY                              OSMemQty = (OS_OBJ_QTY)0;                  /* 创建的内存块数量 类型U16 Number of memory partitions created   */


/*$PAGE*/
/*添加内存块 到调试列表，目前不知道有什么用
************************************************************************************************************************
*                                           ADD MEMORY PARTITION TO DEBUG LIST
*
* Description : This function is called by OSMemCreate() to add the memory partition to the debug table.
*
* Arguments   : p_mem    Is a pointer to the memory partition
*
* Returns     : none
*
* Note(s)    : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/

#if OS_CFG_DBG_EN > 0u
void  OS_MemDbgListAdd (OS_MEM  *p_mem)
{
    p_mem->DbgPrevPtr               = (OS_MEM *)0;
    if (OSMemDbgListPtr == (OS_MEM *)0) {
        p_mem->DbgNextPtr           = (OS_MEM *)0;
    } else {
        p_mem->DbgNextPtr           =  OSMemDbgListPtr;
        OSMemDbgListPtr->DbgPrevPtr =  p_mem;
    }
    OSMemDbgListPtr                 =  p_mem;
}
#endif





/*
************************************************************************************************************************
*                                               CREATE A MEMORY PARTITION
*
* Description : Create a fixed-sized memory partition that will be managed by uC/OS-III.
*
* Arguments   : p_mem    is a pointer to a memory partition control block which is allocated in user memory space.
*
*               p_name   is a pointer to an ASCII string to provide a name to the memory partition.
*
*               p_addr   is the starting address of the memory partition
*
*               n_blks   is the number of memory blocks to create from the partition.
*
*               blk_size is the size (in bytes) of each block in the memory partition.
*
*               p_err    is a pointer to a variable containing an error message which will be set by this function to
*                        either:
*
*                            OS_ERR_NONE                    if the memory partition has been created correctly.
*                            OS_ERR_ILLEGAL_CREATE_RUN_TIME if you are trying to create the memory partition after you
*                                                             called OSSafetyCriticalStart().
*                            OS_ERR_MEM_INVALID_BLKS        user specified an invalid number of blocks (must be >= 2)
*                            OS_ERR_MEM_INVALID_P_ADDR      if you are specifying an invalid address for the memory
*                                                           storage of the partition or, the block does not align on a
*                                                           pointer boundary
*                            OS_ERR_MEM_INVALID_SIZE        user specified an invalid block size
*                                                             - must be greater than the size of a pointer
*                                                             - must be able to hold an integral number of pointers
* Returns    : none
************************************************************************************************************************
*/

void  umem_block_create (OS_MEM       *p_mem,
                         CPU_CHAR     *p_name,
                         void         *p_addr,
                         OS_MEM_QTY    n_blks,
                         OS_MEM_SIZE   blk_size,
                         OS_MEM_ERR       *p_err)
{
#if OS_CFG_ARG_CHK_EN > 0u
    CPU_DATA       align_msk;
#endif
    OS_MEM_QTY     i;
    OS_MEM_QTY     loops;
    CPU_INT08U    *p_blk;
    void         **p_link;
    



#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_MEM_ERR *)0) {
       // assert("err point is 0 \n");
        return;
    }
#endif


#if OS_CFG_ARG_CHK_EN > 0u
    if (p_addr == (void *)0) {                              /* Must pass a valid address for the memory part.         */
       *p_err   = OS_ERR_MEM_INVALID_P_ADDR;
        return;
    }
    if (n_blks < (OS_MEM_QTY)2) {                           /* Must have at least 2 blocks per partition              */
       *p_err = OS_ERR_MEM_INVALID_BLKS;
        return;
    }
    if (blk_size < sizeof(void *)) {                        /* Must contain space for at least a pointer              */
       *p_err = OS_ERR_MEM_INVALID_SIZE;
        return;
    }
    align_msk = sizeof(void *) - 1u;
    if (align_msk > 0) {
        if (((CPU_ADDR)p_addr & align_msk) != 0u){          /* Must be pointer size aligned                           */
           *p_err = OS_ERR_MEM_INVALID_P_ADDR;
            return;
        }
        if ((blk_size & align_msk) != 0u) {                 /* Block size must be a multiple address size             */
           *p_err = OS_ERR_MEM_INVALID_SIZE;
            return;
        }
    }
#endif

    p_link = (void **)p_addr;                               /* Create linked list of free memory blocks               */
    p_blk  = (CPU_INT08U *)p_addr;
    loops  = n_blks - 1u;
    for (i = 0u; i < loops; i++) {
        p_blk +=  blk_size;
       *p_link = (void  *)p_blk;                            /* Save pointer to NEXT block in CURRENT block            */
        p_link = (void **)(void *)p_blk;                    /* Position     to NEXT block                             */
    }
   *p_link             = (void *)0;                         /* Last memory block points to NULL                       */

    OS_CRITICAL_ENTER();
    p_mem->Type        = OS_OBJ_TYPE_MEM;                   /* Set the type of object                                 */
    p_mem->NamePtr     = p_name;                            /* Save name of memory partition                          */
    p_mem->AddrPtr     = p_addr;                            /* Store start address of memory partition                */
    p_mem->FreeListPtr = p_addr;                            /* Initialize pointer to pool of free blocks              */
    p_mem->NbrFree     = n_blks;                            /* Store number of free blocks in MCB                     */
    p_mem->NbrMax      = n_blks;
    p_mem->BlkSize     = blk_size;                          /* Store block size of each memory blocks                 */

#if OS_CFG_DBG_EN > 0u
    OS_MemDbgListAdd(p_mem);
#endif

    OSMemQty++;

    OS_CRITICAL_EXIT_NO_SCHED();
   *p_err = OS_ERR_NONE;
}

/*$PAGE*/
/*
************************************************************************************************************************
*                                                  GET A MEMORY BLOCK
*
* Description : Get a memory block from a partition
*
* Arguments   : p_mem   is a pointer to the memory partition control block
*
*               p_err   is a pointer to a variable containing an error message which will be set by this function to
*                       either:
*
*                       OS_ERR_NONE               if the memory partition has been created correctly.
*                       OS_ERR_MEM_INVALID_P_MEM  if you passed a NULL pointer for 'p_mem'
*                       OS_ERR_MEM_NO_FREE_BLKS   if there are no more free memory blocks to allocate to the caller
*
* Returns     : A pointer to a memory block if no error is detected
*               A pointer to NULL if an error is detected
************************************************************************************************************************
*/

void  *umem_block_malloc (OS_MEM  *p_mem,
                          OS_MEM_ERR  *p_err)
{
    void    *p_blk;

#ifdef OS_SAFETY_CRITICAL
    /* 没传入 错误指针 */
    if (p_err == (OS_MEM_ERR *)0) {
        // printf("err point is 0 \n");
        return ((void *)0);
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u
    /* 没传入分配地址的指针 */
    if (p_mem == (OS_MEM *)0) {                             /* Must point to a valid memory partition                 */
       *p_err  = OS_ERR_MEM_INVALID_P_MEM;
        return ((void *)0);
    }
#endif

    CPU_CRITICAL_ENTER();
    /* 看空闲块的数量不为0 */
    if (p_mem->NbrFree == (OS_MEM_QTY)0) {                  /* See if there are any free memory blocks                */
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_MEM_NO_FREE_BLKS;                    /* No,  Notify caller of empty memory partition           */
        return ((void *)0);                                 /*      Return NULL pointer to caller                     */
    }
    p_blk              = p_mem->FreeListPtr;                /* Yes, point to next free memory block                   */
    p_mem->FreeListPtr = *(void **)p_blk;                   /*      Adjust pointer to new free list                   */
    p_mem->NbrFree--;                                       /*      One less memory block in this partition           */
    CPU_CRITICAL_EXIT();
   *p_err = OS_ERR_NONE;                                    /*      No error                                          */
    return (p_blk);                                         /*      Return memory block to caller                     */
}

/*$PAGE*/
/*
************************************************************************************************************************
*                                                 RELEASE A MEMORY BLOCK
*
* Description : Returns a memory block to a partition
*
* Arguments   : p_mem    is a pointer to the memory partition control block
*
*               p_blk    is a pointer to the memory block being released.
*
*               p_err    is a pointer to a variable that will contain an error code returned by this function.
*
*                            OS_ERR_NONE               if the memory block was inserted into the partition
*                            OS_ERR_MEM_FULL           if you are returning a memory block to an already FULL memory
*                                                      partition (You freed more blocks than you allocated!)
*                            OS_ERR_MEM_INVALID_P_BLK  if you passed a NULL pointer for the block to release.
*                            OS_ERR_MEM_INVALID_P_MEM  if you passed a NULL pointer for 'p_mem'
************************************************************************************************************************
*/

void  umem_block_free (OS_MEM  *p_mem,
                       void    *p_blk,
                       OS_MEM_ERR  *p_err)
{

#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_MEM_ERR *)0) {
         // printf("err point is 0 \n");
        return;
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u
    if (p_mem == (OS_MEM *)0) {                             /* Must point to a valid memory partition                 */
       *p_err  = OS_ERR_MEM_INVALID_P_MEM;
        return;
    }
    if (p_blk == (void *)0) {                               /* Must release a valid block                             */
       *p_err  = OS_ERR_MEM_INVALID_P_BLK;
        return;
    }
#endif

    CPU_CRITICAL_ENTER();
    if (p_mem->NbrFree >= p_mem->NbrMax) {                  /* Make sure all blocks not already returned              */
        CPU_CRITICAL_EXIT();
       *p_err = OS_ERR_MEM_FULL;
        return;
    }
    *(void **)p_blk    = p_mem->FreeListPtr;                /* Insert released block into free block list             */
    p_mem->FreeListPtr = p_blk;
    p_mem->NbrFree++;                                       /* One more memory block in this partition                */
    CPU_CRITICAL_EXIT();
   *p_err              = OS_ERR_NONE;                       /* Notify caller that memory block was released           */
}




/*$PAGE*/
/*
************************************************************************************************************************
*                                                  GET A MEMORY FREE BLOCK
*
* Description : Get a memory block from a partition
*
* Arguments   : p_mem   is a pointer to the memory partition control block
*
*        
* Returns     : The number of free block
*             
************************************************************************************************************************
*/

CPU_SIZE umem_get_free_block(OS_MEM* p_mem)
{
    return p_mem->NbrFree;
}




/*$PAGE*/
/*
************************************************************************************************************************
*                                                  GET A MEMORY FREE BLOCK
*
* Description : Get a memory block from a partition
*
* Arguments   : p_mem   is a pointer to the memory partition control block
*
*        
* Returns     : The number of max block
*             
************************************************************************************************************************
*/
CPU_SIZE umem_get_max_block(OS_MEM* p_mem)
{
    return p_mem->NbrMax;
}







